﻿using System.Collections.Generic;

namespace Centido.Core.Test
{
    using System;
    using Microsoft.VisualStudio.TestTools.UnitTesting;

    /// <summary>
    ///   Testing deleting and restoring
    /// </summary>
    [TestClass]
    public class DeletingAndRestoringTest
    {
        /// <summary>
        ///   Domain name
        /// </summary>
        public const string DomainName = "FullAdminRights";

        /// <summary>
        ///   Domain
        /// </summary>
        private Domain _domain;

        /// <summary>
        ///   A table in the domain
        /// </summary>
        private Table _table;

        /// <summary>
        ///   Name of that table
        /// </summary>
        private string _tableName;

        /// <summary>
        ///   Initializing the test
        /// </summary>
        [TestInitialize]
        public void Initialize()
        {
            _domain = Domain.Build(DomainConfiguration.Load(DomainName));
            _domain.HasRecycleBin = true;

            _domain.DeleteAllTables();

            _tableName = StringProperty.RandomIdentifier();

            _table = _domain.AddTable(_tableName);

            _table.GrantAll();
        }

        /// <summary>
        ///   Cleaning up after the test
        /// </summary>
        [TestCleanup]
        public void CleanUp()
        {
            _domain.DeleteAllTables();
        }

        /// <summary>
        ///   Restoring value after deletion
        /// </summary>
        [TestMethod]
        public void RestoringValueAfterDeletion()
        {
            //Creating a user-defined column
            var column = _table.AddColumn("user_column", ColumnType.Integer);

            //Inserting a value
            const int columnValue = 1;

            var rowId = _table.InsertRow(column.Value(1));

            Assert.AreEqual(_table.RowCount, 1);

            //Deleting the value
            _table.DeleteRow(rowId);

            Assert.AreEqual(_table.RowCount, 0);

            //Restoring the value
            _table.RestoreRow(rowId);

            Assert.AreEqual(_table.RowCount, 1);

            //Reading column value
            var inserted = column.EmptyValue();

            _table.ReadRow(rowId, inserted);

            Assert.AreEqual(columnValue, inserted.GetInteger());
        }

        /// <summary>
        /// Reading system columns of deleted value
        /// </summary>
        [TestMethod]
        public void ReadingSystemColumnsOfDeletedValue()
        {
            //Inserting a value
            var rowId = _table.InsertRow();

            Assert.AreEqual(_table.RowCount, 1);
            Assert.AreEqual(_table.DeletedRowCount, 0);
            Assert.AreEqual(_table.InternalRowCount, 1);

            //Deleting the value
            _table.DeleteRow(rowId);

            Assert.AreEqual(_table.RowCount, 0);
            Assert.AreEqual(_table.DeletedRowCount, 1);
            Assert.AreEqual(_table.InternalRowCount, 1);

            //Reading system columns
            var isActiveValue = _table.SystemColumns.IsActive.EmptyValue();
            var deletedByValue = _table.SystemColumns.DeletedBy.EmptyValue();
            var deletedOnValue = _table.SystemColumns.DeletedOn.EmptyValue();

            _table.ReadRow(rowId, DataSource.InternalTable, isActiveValue, deletedByValue, deletedOnValue);

            Assert.AreEqual(isActiveValue.GetBoolean(), false);
            Assert.IsNotNull(deletedByValue.GetString());
            Assert.IsNotNull(deletedOnValue.GetDateTime());
        }

        /// <summary>
        ///   Reading system columns when deleting on cascade (bug fix).
        /// </summary>
        [TestMethod]
        public void ReadingSystemColumnsWhenDeletingOnCascade()
        {
            var anotherTable = _domain.AddTable(StringProperty.RandomIdentifier());
            anotherTable.GrantAll();

            var refColumn = RefColumn.Add(_table, StringProperty.RandomIdentifier());

            refColumn.AddTarget(anotherTable).HasCascadeDelete = true;

            var firstId = anotherTable.InsertRow();

            var secondId = _table.InsertRow(refColumn.Value(firstId));

            anotherTable.DeleteRow(firstId);

            // Reading the system columns
            var deletedBy = _table.SystemColumns.DeletedBy.Read(secondId, DataSource.InternalTable).GetString();
            var deletedOn = _table.SystemColumns.DeletedOn.Read(secondId, DataSource.InternalTable).GetDateTime();

            Assert.IsNotNull(deletedBy);
            Assert.IsNotNull(deletedOn);
        }

        /// <summary>
        /// Reading system columns after restoring
        /// </summary>
        [TestMethod]
        public void ReadingSystemColumnsAfterRestoring()
        {
            //Inserting a value
            var rowId = Guid.NewGuid();

            _table.InsertRow(rowId);

            //Deleting the value
            _table.DeleteRow(rowId);

            Assert.AreEqual(_table.RowCount, 0);
            Assert.AreEqual(_table.DeletedRowCount, 1);
            Assert.AreEqual(_table.InternalRowCount, 1);

            //Restoring the value
            _table.RestoreRow(rowId);

            Assert.AreEqual(_table.RowCount, 1);
            Assert.AreEqual(_table.DeletedRowCount, 0);
            Assert.AreEqual(_table.InternalRowCount, 1);

            //Reading system columns
            var isActiveValue = _table.SystemColumns.IsActive.EmptyValue();
            var deletedByValue = _table.SystemColumns.DeletedBy.EmptyValue();
            var deletedOnValue = _table.SystemColumns.DeletedOn.EmptyValue();

            _table.ReadRow(rowId, DataSource.InternalTable, isActiveValue, deletedByValue, deletedOnValue);

            Assert.AreEqual(isActiveValue.GetBoolean(), true);
            Assert.IsNull(deletedByValue.GetString());
            Assert.IsNull(deletedOnValue.GetDateTime());
        }

        /// <summary>
        /// Soft deleting a chain of rows
        /// </summary>
        [TestMethod]
        public void SoftDeletingChainOfRows()
        {
            //Creating the third table
            var thirdTable = _domain.AddTable("third_table");
            thirdTable.GrantAll();

            //Creating the second table
            var secondTable = _domain.AddTable("second_table");
            secondTable.GrantAll();

            var secondTableColumn = RefColumn.Add(secondTable, "ref");

            secondTableColumn.AddTarget(thirdTable).HasCascadeDelete = true;

            //Adding reference to the second table
            var firstTableColumn = RefColumn.Add(_table, "ref");

            firstTableColumn.AddTarget(secondTable).HasCascadeDelete = true;

            //Inserting values
            var thirdTableRowId = thirdTable.InsertRow();
            var secondTableRowId = secondTable.InsertRow(secondTableColumn.Value(thirdTableRowId));
            _table.InsertRow(firstTableColumn.Value(secondTableRowId));

            Assert.AreEqual(_table.RowCount, 1);
            Assert.AreEqual(secondTable.RowCount, 1);
            Assert.AreEqual(thirdTable.RowCount, 1);

            //Deleting the chain of rows
            thirdTable.DeleteRow(thirdTableRowId);

            Assert.AreEqual(_table.RowCount, 0);
            Assert.AreEqual(secondTable.RowCount, 0);
            Assert.AreEqual(thirdTable.RowCount, 0);
            Assert.AreEqual(_table.DeletedRowCount, 1);
            Assert.AreEqual(secondTable.DeletedRowCount, 1);
            Assert.AreEqual(thirdTable.DeletedRowCount, 1);
        }

        /// <summary>
        /// Soft deleting a chain of rows wuth recursion (involves trigger recursion)
        /// </summary>
        [TestMethod]
        public void SoftDeletingChainOfRowsWithRecursion()
        {
            //Adding a recursive reference
            var recursiveRef = RefColumn.Add(_table, StringProperty.RandomIdentifier());

            recursiveRef.AddTarget(_table).HasCascadeDelete = true;

            //Adding another table
            var anotherTable = _domain.AddTable(StringProperty.RandomIdentifier());

            anotherTable.GrantAll();

            //Adding reference from another table
            var refColumn = RefColumn.Add(anotherTable, StringProperty.RandomIdentifier());

            refColumn.AddTarget(_table).HasCascadeDelete = true;

            //Inserting values
            var firstRowId = _table.InsertRow();
            var secondRowId = _table.InsertRow(recursiveRef.Value(firstRowId));

            anotherTable.InsertRow(refColumn.Value(secondRowId));

            Assert.AreEqual(_table.RowCount, 2);
            Assert.AreEqual(anotherTable.RowCount, 1);

            //Soft deleting the chain of rows
            _table.DeleteRow(firstRowId);

            Assert.AreEqual(_table.RowCount, 0);
            Assert.AreEqual(anotherTable.RowCount, 0);
        }


        /// <summary>
        ///   Soft deleting and restoring a chain of rows with reference redundancy (bug fix)
        /// </summary>
        [TestMethod]
        public void SoftDeletingAndRestoringChainOfRowsWithRefRedundancy()
        {
            var mainTable = _domain.AddTable(StringProperty.RandomIdentifier());
            var childTable = _domain.AddTable(StringProperty.RandomIdentifier());
            var grandChildTable = _domain.AddTable(StringProperty.RandomIdentifier());

            mainTable.GrantAll();
            childTable.GrantAll();
            grandChildTable.GrantAll();

            var childRef = RefColumn.Add(childTable, "ref");
            childRef.AddTarget(mainTable).HasCascadeDelete = true;

            var grandChildRef1 = RefColumn.Add(grandChildTable, "ref_1");
            grandChildRef1.AddTarget(mainTable).HasCascadeDelete = true;

            var grandChildRef2 = RefColumn.Add(grandChildTable, "ref_2");
            grandChildRef2.AddTarget(childTable).HasCascadeDelete = true;

            var id = mainTable.InsertRow();

            var childId = childTable.InsertRow(childRef.Value(id));

            var grandChildId = grandChildTable.InsertRow(grandChildRef1.Value(id), grandChildRef2.Value(childId));

            // Soft deleting the chain of rows
            mainTable.DeleteRow(id);

            Assert.AreEqual(mainTable.RowCount, 0);
            Assert.AreEqual(childTable.RowCount, 0);
            Assert.AreEqual(grandChildTable.RowCount, 0);

            // Restoring the chain
            grandChildTable.RestoreRow(grandChildId);

            Assert.AreEqual(mainTable.RowCount, 1);
            Assert.AreEqual(childTable.RowCount, 1);
            Assert.AreEqual(grandChildTable.RowCount, 1);
        }


        /// <summary>
        ///   Soft deleting and restoring a very long chain of rows (bug fix)
        /// </summary>
        [TestMethod]
        public void SoftDeletingAndRestoringVeryLongChainOfRows()
        {
            var tableList = new List<Table>();

            for (var i = 0; i < 100; i++)
            {
                var table = _domain.AddTable(StringProperty.RandomIdentifier());

                table.GrantAll();

                tableList.Add(table);
            }

            var refColumnList = new List<RefColumn>();

            for (var i = 1; i < 100; i++)
            {
                var refColumn = RefColumn.Add(tableList[i], StringProperty.RandomIdentifier());

                refColumn.AddTarget(tableList[i - 1]).HasCascadeDelete = true;

                refColumnList.Add(refColumn);
            }

            var initialId = tableList[0].InsertRow();
            var previousId = initialId;

            for (var i = 1; i < 100; i++)
            {
                previousId = tableList[i].InsertRow(refColumnList[i - 1].Value(previousId));
            }

            // Deleting the chain of rows
            tableList[0].DeleteRow(initialId);

            for (var i = 0; i < 100; i++)
                Assert.AreEqual(tableList[i].RowCount, 0);

            // Restoring the chain of rows
            tableList[99].RestoreRow(previousId);

            for (var i = 0; i < 100; i++)
                Assert.AreEqual(tableList[i].RowCount, 1);
        }

        /// <summary>
        /// Restoring chain of rows
        /// </summary>
        [TestMethod]
        public void RestoringChainOfRows()
        {
            //Creating the third table
            var thirdTable = _domain.AddTable("third_table");
            thirdTable.GrantAll();

            //Creating the second table
            var secondTable = _domain.AddTable("second_table");
            secondTable.GrantAll();

            var secondTableColumn = RefColumn.Add(secondTable, "ref");

            secondTableColumn.AddTarget(thirdTable).HasCascadeDelete = true;

            //Adding reference to the second table
            var firstTableColumn = RefColumn.Add(_table, "ref");

            firstTableColumn.AddTarget(secondTable).HasCascadeDelete = true;

            //Inserting values
            var thirdTableRowId = thirdTable.InsertRow();
            var secondTableRowId = secondTable.InsertRow(secondTableColumn.Value(thirdTableRowId));
            var firstTableRowId = _table.InsertRow(firstTableColumn.Value(secondTableRowId));

            //Deleting the chain of rows
            thirdTable.DeleteRow(thirdTableRowId);

            //Setting cascade delete to false (for complexity)
            secondTableColumn.GetTarget(thirdTable).HasCascadeDelete = false;
            firstTableColumn.GetTarget(secondTable).HasCascadeDelete = false;

            //Restoring the chain of rows
            _table.RestoreRow(firstTableRowId);

            Assert.AreEqual(_table.RowCount, 1);
            Assert.AreEqual(secondTable.RowCount, 1);
            Assert.AreEqual(thirdTable.RowCount, 1);
            Assert.AreEqual(_table.DeletedRowCount, 0);
            Assert.AreEqual(secondTable.DeletedRowCount, 0);
            Assert.AreEqual(thirdTable.DeletedRowCount, 0);
        }

        /// <summary>
        /// Hard deleting chain of rows
        /// </summary>
        [TestMethod]
        public void HardDeletingChainOfRows()
        {
            //Creating the third table
            var thirdTable = _domain.AddTable("third_table");
            thirdTable.GrantAll();

            //Creating the second table
            var secondTable = _domain.AddTable("second_table");
            secondTable.GrantAll();

            var secondTableColumn = RefColumn.Add(secondTable, "ref");

            secondTableColumn.AddTarget(thirdTable).HasCascadeDelete = true;

            //Adding reference to the second table
            var firstTableColumn = RefColumn.Add(_table, "ref");

            firstTableColumn.AddTarget(secondTable).HasCascadeDelete = true;

            //Inserting values
            var thirdTableRowId = thirdTable.InsertRow();
            var secondTableRowId = secondTable.InsertRow(secondTableColumn.Value(thirdTableRowId));
            _table.InsertRow(firstTableColumn.Value(secondTableRowId));

            //Deleting the chain of rows
            thirdTable.DeleteRow(thirdTableRowId);

            //Setting cascade delete to false (for complexity)
            secondTableColumn.GetTarget(thirdTable).HasCascadeDelete = false;
            firstTableColumn.GetTarget(secondTable).HasCascadeDelete = false;

            //Hard deleting the chain of rows
            thirdTable.DeleteRow(thirdTableRowId, DataSource.InternalTable);

            Assert.AreEqual(_table.RowCount, 0);
            Assert.AreEqual(secondTable.RowCount, 0);
            Assert.AreEqual(thirdTable.RowCount, 0);
            Assert.AreEqual(_table.DeletedRowCount, 0);
            Assert.AreEqual(secondTable.DeletedRowCount, 0);
            Assert.AreEqual(thirdTable.DeletedRowCount, 0);
            Assert.AreEqual(_table.InternalRowCount, 0);
            Assert.AreEqual(secondTable.InternalRowCount, 0);
            Assert.AreEqual(thirdTable.InternalRowCount, 0);
        }

        /// <summary>
        /// Hard deleting a chain of rows with recursion (involves trigger recursion)
        /// </summary>
        [TestMethod]
        public void HardDeletingChainOfRowsWithRecursion()
        {
            //Adding a recursive reference
            var recursiveRef = RefColumn.Add(_table, StringProperty.RandomIdentifier());

            recursiveRef.AddTarget(_table);

            //Adding another table
            var anotherTable = _domain.AddTable(StringProperty.RandomIdentifier());

            anotherTable.GrantAll();

            //Adding reference from another table
            var refColumn = RefColumn.Add(anotherTable, StringProperty.RandomIdentifier());

            refColumn.AddTarget(_table);

            //Inserting values
            var firstRowId = _table.InsertRow();
            var secondRowId = _table.InsertRow(recursiveRef.Value(firstRowId));

            anotherTable.InsertRow(refColumn.Value(secondRowId));

            Assert.AreEqual(_table.InternalRowCount, 2);
            Assert.AreEqual(anotherTable.InternalRowCount, 1);

            //Soft deleting the chain of rows
            _table.DeleteRow(firstRowId, DataSource.InternalTable);

            Assert.AreEqual(_table.InternalRowCount, 0);
            Assert.AreEqual(anotherTable.InternalRowCount, 0);
        }

        /// <summary>
        ///   Hard deleting a chain of rows with reference redundancy.
        /// </summary>
        [TestMethod]
        public void HardDeletingChainOfRowsWithRefRedundancy()
        {
            var mainTable = _domain.AddTable(StringProperty.RandomIdentifier());
            var childTable = _domain.AddTable(StringProperty.RandomIdentifier());
            var grandChildTable = _domain.AddTable(StringProperty.RandomIdentifier());

            mainTable.GrantAll();
            childTable.GrantAll();
            grandChildTable.GrantAll();

            var childRef = RefColumn.Add(childTable, "ref");
            childRef.AddTarget(mainTable).HasCascadeDelete = true;

            var grandChildRef1 = RefColumn.Add(grandChildTable, "ref_1");
            grandChildRef1.AddTarget(mainTable).HasCascadeDelete = true;

            var grandChildRef2 = RefColumn.Add(grandChildTable, "ref_2");
            grandChildRef2.AddTarget(childTable).HasCascadeDelete = true;

            var id = mainTable.InsertRow();

            var childId = childTable.InsertRow(childRef.Value(id));

            grandChildTable.InsertRow(grandChildRef1.Value(id), grandChildRef2.Value(childId));

            mainTable.DeleteRow(id, DataSource.InternalTable);

            Assert.AreEqual(mainTable.InternalRowCount, 0);
            Assert.AreEqual(childTable.InternalRowCount, 0);
            Assert.AreEqual(grandChildTable.InternalRowCount, 0);
        }

        /// <summary>
        /// Restoring with no table permissions
        /// </summary>
        [TestMethod]
        public void RestoringWithNoTablePermissions()
        {
            var rowId = _table.InsertRow();

            Assert.AreEqual(_table.RowCount, 1);

            //Deleting the value
            _table.DeleteRow(rowId);

            Assert.AreEqual(_table.RowCount, 0);

            //Creating new admin
            var adminPassword = StringProperty.RandomPassword();
            var admin = _domain.AddUser(StringProperty.RandomIdentifier(), adminPassword);
            admin.CanLogon = true;
            admin.CanChangeSchema = true;

            //Logging in as admin
            var table = _domain.Logon(admin.Name, adminPassword).GetTable(_table.Name);

            //Restoring the value
            table.RestoreRow(rowId);

            Assert.AreEqual(_table.RowCount, 1);
        }
    }
}